from soaplib.wsgi_soap import SimpleWSGISoapApp
from soaplib.service import soapmethod
from soaplib.serializers.primitive import Array, String, Integer

import hashlib
import datetime

from sqlalchemy.orm.exc import NoResultFound
from elixir import session

from data.models import *
from soap_types import *
from soap_mappers import *
from utils import *

class PyBooklerService(SimpleWSGISoapApp):
    _logged_users = {}
    
    def onMethodExec(self,environ,body,py_params,soap_params):
        soaplogger.debug("********** onMethodExec ************")
        soaplogger.debug("ENV: %s \n BODY: %s \n PYPAR: %s \n SOAPPAR: %s "
                         % (environ, body, py_params, soap_params))

    def onResults(self,environ,py_results,soap_results):
        session.remove()

    def onReturn(self,environ,returnString):
        soaplogger.debug("********** onReturn ************")
        soaplogger.debug("ENV: %s \n RETURN: %s" % (environ, returnString))
    
    @soapmethod(Integer, Integer, _returns=Array(Stats))
    def get_stats(self, idgenre, idauthor):
        result = []
        query = Product.query.join(ProductBooks, Book)
        
        if idauthor:
            author = Author.get_by(id=idauthor)
            query = query.filter(Book.authors.contains(author))
        
        if idgenre:
            genre = Genre.get_by(id=idgenre)
            query = query.filter(Book.genres.contains(genre))

        sold = 0
        for pr in query.all():
            for o in pr.orders: sold += o.quantity
            stat = Stats()
            stat.id = pr.id
            stat.name = pr.name
            stat.edition = pr.edition.name if pr.edition else ""
            stat.prints = len(pr.prints)
            stat.price = round(pr.price, 2)
            stat.sold = sold
            stat.quantity = pr.quantity
            result.append(stat)
            
        return result
    
    @soapmethod(String, String, _returns=UserSoap)
    def user_login(self, user, passwd):
        """
        This soap method checks user and password in database.
        
        return: UserSoap instance; if no user is found id is set to -1
        """
        result = UserSoap()
        try:
            us = User.query.filter_by(username=user, password=passwd).one()
            result = map_user(us)
            tmstp = str(datetime.datetime.now())
            result.tmstp = tmstp
            self._logged_users[tmstp] = (us.id, us.subject.row_type)
        except NoResultFound:
            result.id = -1
        
        return result
    
    @soapmethod(_returns=Array(UserSoap))
    def get_users(self):
        """
        This soap method retrieves all user in database.
        
        return: Array of UserSoap;
        """
        result = []
        
        users = User.query.filter(User.id>1).all()
        result = [map_user(user) for user in users]
           
        return result
    
    @soapmethod(Integer, _returns=Array(AuthorSoap))
    def get_authors(self, id):
        """
        This soap method retrieves all author in database.
        
        return: Array of AuthorSoap;
        """
        result = []
        
        if id:
            user = User.get_by(id=id)
            authors = Author.query.filter_by(id=user.subject.id).all()
        else:
            authors = Author.query.all()
            
        result = [map_author(author) for author in authors]
        return result
    
    @soapmethod(_returns=Array(BookSoap))
    def get_books(self):
        """
        This soap method retrieves all book in database.
        
        return: Array of BookSoap;
        """
        result = []
        
        books = Book.query.all()
        result = [map_book(book) for book in books]
            
        return result
    
    @soapmethod(Integer, _returns=Array(BookshopSoap))
    def get_bookshops(self, id):
        """
        This soap method retrieves all bookshoap in database.
        
        return: Array of BookshopSoap;
        """
        result = []
        
        if id:
            user = User.get_by(id=id)
            bookshops = Bookshop.query.filter_by(id=user.subject.id).all()
        else:
            bookshops = Bookshop.query.all()
            
        print bookshops[0].company_name
        result = [map_bookshop(bookshop) for bookshop in bookshops]
        return result
    
    @soapmethod(_returns=Array(ProductSoap))
    def get_products(self):
        """
        This soap method retrieves all product in database.
        
        return: Array of ProductSoap;
        """
        result = []
        
        products = Product.query.all()
        result = [map_product(product) for product in products]
        
        return result
    
    @soapmethod(Integer, _returns=Array(OrderSoap))
    def get_orders(self, id):
        """
        This soap method retrieves all product in database.
        
        return: Array of OrderSoap;
        """
        result = []
        
        if id:
            orders = Order.query.filter_by(bookshop=Bookshop.get_by(id=id)).all()
        else:
            orders = Order.query.all()
        
        result = [map_order(order) for order in orders]
        return result
    
    @soapmethod(_returns=Array(GenreSoap))
    def get_genres(self):
        """
        This soap method retrieves all product in database.
        
        return: Array of OrderSoap;
        """
        result = []
        
        genres = Genre.query.all()
        result = [map_genre(genre) for genre in genres]
        
        return result
    
    @soapmethod(Integer, _returns=Array(AddressSoap))
    def get_addresses(self, id):
        result = []
        
        subject = Subject.get_by(id=id)
        result = [map_address(address) for address in subject.addresses]
        
        return result
    
    @soapmethod(_returns=Array(EditionSoap))
    def get_editions(self):
        result = []
        
        editions = Edition.query.all()
        result = [map_edition(edition) for edition in editions]
        
        return result
    
    @soapmethod(Integer, _returns=Array(PrintSoap))
    def get_prints(self, id):
        result = []
        
        if id:
            product = Product.get_by(id=id)
            prints = Print.query.filter_by(product=product, date=None).all()
        else:
            prints = Print.query.order_by('-date').all()
        
        result = [map_print(pr) for pr in prints]
        return result
    
    # save services
    @soapmethod(AuthorSoap, _returns=Integer)
    def save_author(self, author):
        if not author:
            return -1
        
        if not author.id:
            authordb = Author()
        else:
            authordb = Author.get_by(id=author.id)
        
        try:
            address = author.addresses[0]
        except (IndexError, TypeError):
            address = None
       
        try:
            addrdb = authordb.addresses[0]
        except (IndexError, TypeError):
            addrdb = None
        
        authordb.name = author.name
        authordb.surname = author.surname
            
        # address handling
        if address and not addrdb:
            addrdb = Address()
            
        if address and addrdb:
            addrdb.address = address.address
            addrdb.city = address.city
            addrdb.country = address.country
            addrdb.postalcode = address.postalcode
            addrdb.province = address.province
            addrdb.type = "head"
            authordb.addresses.append(addrdb)
        
        if not address and addrdb:
            addrdb.delete()
        
        session.commit()
        
        return authordb.id
    
    @soapmethod(AddressSoap, _returns=Integer)
    def save_address(self, address):
        if not address:
            return -1
        if not address.id:
            addressdb = Address()
        else:
            addressdb = Address.get_by(id=address.id)
        
        addressdb.address = address.address
        addressdb.postalcode = address.postalcode
        addressdb.city = address.city
        addressdb.province = address.province
        addressdb.country = address.country
        addressdb.type = address.type
        addressdb.subject = address.subject.id
        
        session.commit()
        
        return addressdb.id
    
    @soapmethod(BookSoap, _returns=Integer)
    def save_book(self, book):
        if not book:
            return -1
        if not book.id:
            bookdb = Book()
        else:
            bookdb = Book.get_by(id=book.id)
            
        bookdb.title = book.title
        bookdb.description = book.description
        
        for author in bookdb.authors:
            if author.id not in book.authors:
                bookdb.authors.remove(author)
        for author in book.authors:
            au = Author.get_by(id=author)
            if au not in bookdb.authors:
                bookdb.authors.append(au)
        
        for genre in bookdb.genres:
            if genre.id not in book.genres:
                bookdb.genres.remove(genre)
        for genre in book.genres:
            ge = Genre.get_by(id=genre)
            if ge not in bookdb.genres:
                bookdb.genres.append(ge)
        
        for product in bookdb.products:
            if product.id not in book.products:
                bookdb.products.remove(product)
        for product in book.products:
            pr = Product.get_by(id=product)
            if pr not in bookdb.products:
                bookdb.products.append(pr)
                
        session.commit()
        return bookdb.id
    
    @soapmethod(BookshopSoap, _returns=Integer)
    def save_bookshop(self, bookshop):
        if not bookshop:
            return -1
        
        if not bookshop.id:
            bookshopdb = Bookshop()
            head_addrdb = Address()
        else:
            bookshopdb = Bookshop.get_by(id=bookshop.id)
            head_addrdb = bookshopdb.addresses[0]
        
        bookshopdb.company_name = bookshop.company_name
        bookshopdb.vat = bookshop.vat
        bookshopdb.iban = bookshop.iban
        
        head_address = bookshop.addresses[0]
        head_addrdb.address = head_address.address
        head_addrdb.city = head_address.city
        head_addrdb.country = head_address.country
        head_addrdb.postalcode = head_address.postalcode
        head_addrdb.province = head_address.province
        head_addrdb.type = "head"
        if head_addrdb not in bookshopdb.addresses:
            bookshopdb.addresses.append(head_addrdb)
        
        try:
            reg_address = bookshop.addresses[1]
        except (IndexError, TypeError):
            reg_address = None
            
        try:
            reg_addrdb = bookshopdb.addresses[1]
        except (IndexError, TypeError):
            reg_addrdb = None
            
        # registered address handling
        if reg_address and not reg_addrdb:
            reg_addrdb = Address()
            
        if reg_address and reg_addrdb:
            reg_addrdb.address = reg_address.address
            reg_addrdb.city = reg_address.city
            reg_addrdb.country = reg_address.country
            reg_addrdb.postalcode = reg_address.postalcode
            reg_addrdb.province = reg_address.province
            reg_addrdb.type = "registered"
            if reg_addrdb not in bookshopdb.addresses:
                bookshopdb.addresses.append(reg_addrdb)
        
        if not reg_address and reg_addrdb:
            reg_addrdb.delete()
        
        session.commit()
        
        return bookshopdb.id
    
    @soapmethod(ProductSoap, _returns=Integer)
    def save_product(self, product):
        if not product:
            return -1
        
        edition = Edition.get_by(id=product.edition)
        
        if not product.id:
            productdb = Product()
        else:
            productdb = Product.get_by(id=product.id)
        
        productdb.name = product.name
        productdb.description = product.description
        productdb.edition = edition
        productdb.price = product.price
        productdb.quantity = product.quantity
        productdb.discount = product.discount
        
        for pb in productdb.product_books:
            pb.delete()
        
        for pb in product.books:
            book = Book.get_by(id=pb.book)
            pbdb = ProductBooks()
            pbdb.book = book
            pbdb.sequence_num = pb.sequence_num
            pbdb.product = productdb
        
        session.commit()
        return productdb.id
    
    @soapmethod(UserSoap, _returns=Integer)
    def save_user(self, user):
        if not user:
            return -1
        
        # query the subject before creating the user db object
        # to avoid autoflush problems
        subject = Subject.get_by(id=user.subject)
        
        if not user.id:
            userdb = User()
        else:
            userdb = User.get_by(id=user.id)
            
        userdb.username = user.username
        if user.password: 
            userdb.password = user.password
        userdb.active = user.active
        userdb.email = user.email
        userdb.subject = subject
        userdb.last_modify_date = datetime.datetime.now()
        
        session.commit()
        
        return userdb.id

    @soapmethod(OrderSoap, _returns=Integer)
    def save_order(self, order):
        if not order:
            return -1
        
        bookshop = Bookshop.get_by(id=order.bookshop.id)
        
        if not order.id:
            orderdb = Order()
        else:
            orderdb = Order.get_by(id=order.id)
        
        orderdb.bookshop = bookshop
        orderdb.date = order.date
        orderdb.status = order.status
        orderdb.total_amount = order.total_amount
        
        for detail in orderdb.detail:
            detail.delete()
        
        for detail in order.detail:
            product = Product.get_by(id=detail.product)
            product.quantity = detail.pamount
            de = OrderDetail()
            de.product = product
            de.quantity = detail.quantity
            de.order = orderdb
            orderdb.detail.append(de)
        
        session.commit()
        return orderdb.id
    
    @soapmethod(GenreSoap, _returns=Integer)
    def save_genre(self, genre):
        if not genre:
            return -1
        
        if Genre.get_by(name=genre.name):
            return -1    
        
        if not genre.id:
            genredb = Genre()
        else:
            genredb = Genre.get_by(id=genre.id)
        
        genredb.name = genre.name
        genredb.description = genre.description
        
        session.commit()
        
        return genredb.id
    
    @soapmethod(EditionSoap, _returns=Integer)
    def save_edition(self, edition):
        if not edition:
            return -1
        
        if Edition.get_by(name=edition.name):
            return -1
        
        if not edition.id:
            editiondb = Edition()
        else:
            editiondb = Edition.get_by(id=edition.id)
        
        editiondb.name = edition.name
        editiondb.description = edition.description
        
        session.commit()
        return editiondb.id
    
    @soapmethod(PrintSoap, _returns=Integer)
    def save_print(self, pr):
        if not pr:
            return -1
        
        product = Product.get_by(id=pr.product)
        
        if not pr.id:
            printdb = Print()
        else:
            printdb = Print.get_by(id=pr.id)
        
        printdb.quantity= pr.quantity
        printdb.product = product
        
        session.commit()
        return printdb.id
    
    @soapmethod(Integer)
    def confirm_print(self, id):
        
        pr = Print.get_by(id=id)
        pr.date = datetime.datetime.now()
        
        session.commit()
    
    # delete services
    
    @soapmethod(AuthorSoap)
    def delete_author(self, author):
        authordb = Author.get_by(id=author.id)
        authordb.delete()
            
        session.commit()
    
    delete_author = soapmethod(delete_author, AuthorSoap)

    @soapmethod(BookSoap)
    def delete_book(self, book):
        bookdb = Book.get_by(id=book.id)
        bookdb.delete()
            
        session.commit()
    
    @soapmethod(BookshopSoap)
    def delete_bookshop(self, bookshop):
        bookshopdb = Bookshop.get_by(id=bookshop.id)
        bookshopdb.delete()
            
        session.commit()
    
    @soapmethod(ProductSoap)
    def delete_product(self, product):
        productdb = Product.get_by(id=product.id)
        productdb.delete()
            
        session.commit()
    
    @soapmethod(OrderSoap)
    def delete_order(self, order):
        orderdb = Order.get_by(id=order.id)
        
        for det in orderdb.detail:
            det.product.quantity += det.quantity
        
        orderdb.delete()
        session.commit()
    
    @soapmethod(UserSoap)
    def delete_user(self, user):
        userdb = User.get_by(id=user.id)
        userdb.delete()
            
        session.commit()
        
    @soapmethod(Integer)
    def delete_print(self, id):
        printdb = Print.get_by(id=id)
        printdb.delete()
            
        session.commit()
        